package com.testing.app;

import com.google.common.io.Files;
import com.testing.DriverSelf.AppDriver;
import com.testing.DriverSelf.MobileBrowserDriver;
import com.testing.common.AutoUtils;
import io.appium.java_client.AppiumBy;
import io.appium.java_client.MultiTouchAction;
import io.appium.java_client.TouchAction;
import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.touch.LongPressOptions;
import io.appium.java_client.touch.WaitOptions;
import io.appium.java_client.touch.offset.ElementOption;
import io.appium.java_client.touch.offset.PointOption;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;

import java.io.File;
import java.io.IOException;
import java.time.Duration;

/**
 * @Classname AppKeyword
 * @Description 类型说明
 * @Date 2022/10/26 20:25
 * @Created by 特斯汀Roy
 */
//继承autoTools中封装的log和参数等公用操作。
public class AppKeyword extends AutoUtils {

    private AndroidDriver driver;

    public AndroidDriver getDriver() {
        return driver;
    }

    /**
     * 再指定端口启动appium服务器，并且记录日志信息
     *
     * @param port
     * @return
     */
    public boolean startAppiumServer(String port) {
        try {
            log.info("正在启动appium服务端。。。。。。");
            String logPath = "log/appiumServerLog/Appium" + dateStr("yyyyMMdd-HH：mm：ss");
            Runtime.getRuntime().exec("cmd /c start appium -p " + port + "  --log-timestamp --local-timezone -g " + logPath);
            return true;
        } catch (IOException e) {
            log.error("启动服务端失败。。。。");
            return false;
        }

    }

    /**
     * 启动app应用。
     *
     * @param port
     * @param packageName
     * @param activityName
     * @param deviceName
     * @return
     */
    public boolean startApp(String port, String packageName, String activityName, String deviceName) {
        try {
            log.info("正在启动应用……");
            AppDriver app = new AppDriver(port, packageName, activityName, deviceName);
            driver = app.getDriver();
            return true;
        } catch (Exception e) {
            log.error("启动应用失败！请检查appium日志");
            return false;
        }
    }

    /**
     * 如果用browser模式进行启动，那么要用get方法来访问网页。
     *
     * @param url
     * @return
     */
    public boolean visitWeb(String url) {
        try {
            log.info("浏览器访问" + url);
            driver.get(url);
            return true;
        } catch (Exception e) {
            log.error("访问失败", e.fillInStackTrace());
            return false;
        }
    }


    /***
     * 通过browser完成启动。
     * @param port
     * @param deviceName
     * @return
     */
    public boolean startBrowser(String port, String deviceName) {
        try {
            log.info("正在启动原生浏览器……");
            MobileBrowserDriver mbd = new MobileBrowserDriver(port, deviceName);
            driver = mbd.getDriver();
            return true;
        } catch (Exception e) {
            log.error("启动应用失败！请检查appium日志");
            return false;
        }
    }


    /**
     * 默认使用xpath作为定位方式
     *
     * @param xpath
     * @return
     */
    public boolean click(String xpath) {
        try {
            log.info("正在定位" + xpath + "元素");
            driver.findElement(By.xpath(xpath)).click();
            return true;
        } catch (Exception e) {
            log.error("定位" + xpath + "元素失败", e.fillInStackTrace());
            takeScreenshot("app点击");
            return false;
        }
    }

    /***
     * 真正常用的定位方法三种： id  aid  xpath
     * 定义一个规则，如果开头遇到{id}那就用id定位，遇到{aid}就用aid定位。
     * @return
     */
    public boolean clickAll(String locator) {
        try {
            log.info("正在定位" + locator + "元素");
            if (locator.startsWith("{id}")) {
                locator = locator.substring(4);
                driver.findElement(By.id(locator)).click();
            } else if (locator.startsWith("{aid}")) {
                locator = locator.substring(5);
                driver.findElement(AppiumBy.accessibilityId(locator)).click();
            } else if (locator.startsWith("{xpath}")) {
                locator = locator.substring(7);
                driver.findElement(By.xpath(locator)).click();
            } else {
                driver.findElement(By.xpath(locator)).click();
            }
            return true;
        } catch (Exception e) {
            log.error("定位" + locator + "元素失败", e.fillInStackTrace());
            return false;
        }
    }

    public boolean input(String xpath, String content) {
        try {
            log.info("正在向" + xpath + "输入" + content);
            driver.findElement(By.xpath(xpath)).clear();
            driver.findElement(By.xpath(xpath)).sendKeys(content);
            return true;
        } catch (Exception e) {
            log.error("输入内容失败", e.fillInStackTrace());
            takeScreenshot("app输入");
            return false;
        }

    }

    public boolean halt(String second) {
        try {
            log.info("等待" + second + "秒");
            float time = Float.parseFloat(second);
            int millis = (int) (time * 1000);
            Thread.sleep(millis);
            return true;
        } catch (Exception e) {
            log.error("多半是数字转换格式有问题");
            log.error("等待出现问题:" + e.fillInStackTrace());
            return false;
        }
    }

    /***
     * 点击不确定是否会出现的元素
     * @return
     */
    public boolean clickUnSure(String xpath) {
        try {
//            System.out.println(driver.getPageSource());
            log.info("正在尝试点击" + xpath + "元素");
            driver.findElement(By.xpath(xpath)).click();
            return true;
        } catch (Exception e) {
            log.info("没有" + xpath + "元素，不需要点击");
            return false;
        }
    }


    /******************************adb封装操作方法*************************************/
    /**
     * 直接点击对应坐标
     *
     * @param xAxis
     * @param yAxis
     */
    public boolean adbTap(String xAxis, String yAxis) {
        try {
            runCmd("adb shell input tap " + xAxis + " " + yAxis);
            log.info(String.format("已通过坐标位置（%s,%s）使用adb进行点击，请留意执行效果", xAxis, yAxis));
            return true;
        } catch (Exception e) {
            log.error(String.format("通过坐标位置（%s,%s）使用adb进行点击失败，请注意检查", xAxis, yAxis), e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 通过屏幕分辨率的比例，转换为元素坐标进行点击操作
     *
     * @param xRate 操作坐标x轴在屏幕中所占比例
     * @param yRate 操作坐标y轴在屏幕中所占比例
     */
    public void tapAdbRateWithScreen(String xRate, String yRate) {
        try {
            //对于屏幕的比例
            float xr = Float.parseFloat(xRate);
            float yr = Float.parseFloat(yRate);
            //屏幕尺寸*比例得到坐标位置
            int width = driver.manage().window().getSize().getWidth();
            log.info("屏幕宽" + width);
            int height = driver.manage().window().getSize().getHeight();
            log.info("屏幕高" + height);
            int x = (int) (xr * width);
            int y = (int) (yr * height);
            log.info("点击位置是" + x + "," + y);
            adbTap(String.valueOf(x), String.valueOf(y));
        } catch (NumberFormatException exception) {
            exception.printStackTrace();
        }
    }

    /**
     * adb回车操作
     *
     * @return
     */
    public boolean adbEnter() {
        try {
            log.info("通过adb按下回车");
            runCmd("adb shell input keyevent 66");
            return true;
        } catch (Exception e) {
            log.error("按下回车失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 调用adb input 方法进行输入。
     *
     * @param text
     */
    public boolean adbText(String text) {
        try {
            runCmd("adb shell input text " + text);
            log.info("用adb输入" + text);
            return true;
        } catch (Exception e) {
            log.error("通过adb输入失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 通过cmd调用adb按键事件
     *
     * @param keycode
     */
    public boolean adbPressKey(String keycode) {
        try {
            String cmd = " adb shell input keyevent " + keycode;
            runCmd(cmd);
            return true;
        } catch (Exception e) {
            log.error("通过adb执行按键事件失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 调用adb滑动,注意，缩短参数m时间，即可实现长按
     */
    public boolean adbSwipe(String i, String j, String k, String l, String m) {
        try {
            runCmd("adb shell input swipe " + i + " " + j + " " + k + " " + l + " " + m);
            log.info("滑动完成");
            return true;
        } catch (Exception e) {
            log.error("通过adb执行滑动失败", e.fillInStackTrace());
            return false;
        }
    }

    /*******************************appium提供的特殊操作方法。********************************************/
    /**
     * 使用appium的touchaction类点击坐标
     *
     * @param x
     * @param y
     * @return
     */
    public boolean appiumTap(String x, String y) {
        try {
            log.info("用appium点击坐标" + x + "," + y);
            int xOffSet = Integer.parseInt(x);
            int yOffSet = Integer.parseInt(y);
            TouchAction action = new TouchAction(driver);
            PointOption tapPoint = PointOption.point(xOffSet, yOffSet);
            action.tap(tapPoint).perform();
            return true;
        } catch (NumberFormatException exception) {
            log.error("点击坐标失败", exception.fillInStackTrace());
            return false;
        }
    }

    /**
     * touchAction实现滑动操作，通过长按+移动完成组合。
     *
     * @param startX
     * @param startY
     * @param endX
     * @param endY
     */
    public boolean appiumSwipe(String startX, String startY, String endX, String endY) {
        try {
            TouchAction action = new TouchAction(driver);
            //学会通过源码学习如何使用方法。
            PointOption startpoint = PointOption.point(Integer.parseInt(startX), Integer.parseInt(startY));
            PointOption endPoint = PointOption.point(Integer.parseInt(endX), Integer.parseInt(endY));
            //通过拼接长按和移动方法完成swipe动作。
            action.longPress(startpoint).moveTo(endPoint).release().perform();
            log.info(String.format("执行滑动操作，从(%s,%s)滑动到(%s,%s)"), startX, startY, endX, endY);
            return true;
        } catch (NumberFormatException e) {
            log.error("滑动操作失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * appium基于元素定位，从指定元素中点开始实现滑动
     *
     * @param xpath
     * @param finX
     * @param finY
     */
    public boolean appiumSwipeElement(String xpath, String finX, String finY) {
        try {
            // string型的参数转换为int型
            int x1 = Integer.parseInt(finX);
            int y1 = Integer.parseInt(finY);
            TouchAction action = new TouchAction(driver);
            // 设置起点和终点
            PointOption movePoint = PointOption.point(x1, y1);
            // 滑动操作由长按起点->移动到终点->松开组成。
            action.longPress(LongPressOptions.longPressOptions()
                    .withElement(ElementOption.element(driver.findElement(By.xpath(xpath))))).moveTo(movePoint)
                    .release().perform();
            return true;
        } catch (Exception e) {
            log.error("执行Appium滑动方法失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 使用appium touchAction类实现长按指定坐标位置
     *
     * @param x
     * @param y
     * @param time
     */
    public boolean appiumHold(String x, String y, String time) {
        try {
            // string转int
            int xAxis = Integer.parseInt(x);
            int yAxis = Integer.parseInt(y);
            int t = Integer.parseInt(time);
            // 指定长按的坐标
            PointOption pressPoint = PointOption.point(xAxis, yAxis);
            // 长按的时间，使用java提供的time类库中的duration类
            Duration last = Duration.ofSeconds(t);
            WaitOptions wait = WaitOptions.waitOptions(last);
            TouchAction action = new TouchAction(driver);
            // 长按坐标->指定按住的时间进行等待
            action.longPress(pressPoint).waitAction(WaitOptions.waitOptions(last)).release().perform();
            return true;
        } catch (NumberFormatException e) {
            log.error("执行Appium滑动方法失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 实现长按某个元素
     *
     * @param xpath
     * @param time
     */
    public boolean appiumHoldEl(String xpath, String time) {
        try {
            int t = Integer.parseInt(time);
            Duration last = Duration.ofSeconds(t);
            TouchAction action = new TouchAction(driver);
            // 定位到一个元素，并且在该元素上长按指定的时长
            action.longPress(LongPressOptions.longPressOptions()
                    .withElement(ElementOption.element(driver.findElement(By.xpath(xpath)))).withDuration(last))
                    .waitAction(WaitOptions.waitOptions(last)).release().perform();
            return true;
        } catch (NumberFormatException e) {
            log.error("执行Appium滑动方法失败", e.fillInStackTrace());
            return false;
        }
    }

    /***
     * 双指缩放操作扩展，使用multiTouchAction类。
     * 由于缩放操作并不一定需要指定滑动范围，所以先写死，要用的时候修改坐标即可。
     */
    public boolean doubleFinger() {
        try {
            // multitouchaction，用于拼接多个touchaction，让他们同时发生。
            MultiTouchAction multiAction = new MultiTouchAction(driver);
            // 创建两个touchaction，分别实现两个手指的动作。
            TouchAction actionone = new TouchAction(driver);
            // 创建等待时间的对象。
            Duration last = Duration.ofMillis(300);
            WaitOptions waitOptions = WaitOptions.waitOptions(last);
            // 创建第一个手指的移动起止点。x坐标增大，y坐标减小，往右上方划
            PointOption pressPointone = PointOption.point(300, 900);
            PointOption movePointone = PointOption.point(400, 800);
            // 滑动操作由长按起点->移动到终点->松开组成。
            actionone.press(pressPointone).waitAction(waitOptions).moveTo(movePointone).waitAction(waitOptions).release();
            // 创建第二个手指的动作。
            TouchAction actiontwo = new TouchAction(driver);
            // x坐标减小，y坐标增大，往左下方划
            PointOption pressPointtwo = PointOption.point(500, 600);
            PointOption movePointtwo = PointOption.point(400, 800);
            // 滑动操作由长按起点->移动到终点->松开组成。
            actiontwo.press(pressPointtwo).waitAction(waitOptions).moveTo(movePointtwo).waitAction(waitOptions).release();
            // 将创建好的两个不同的touchaction，添加到multiaction里，形成一组同步动作，从而完成操作。
            multiAction.add(actionone).add(actiontwo).perform();
            return true;
        } catch (Exception e) {
            log.error("双指缩放失败", e.fillInStackTrace());
            return false;
        }
    }


    /**
     * 调用按键完成回车操作
     */
    public boolean pressEnter() {
        try {
            KeyEvent enter = new KeyEvent();
            driver.pressKey(enter.withKey(AndroidKey.ENTER));
            log.info("执行回车操作");
            return true;
        } catch (Exception e) {
            log.error("执行回车操作失败", e.fillInStackTrace());
            return false;
        }
    }


    /**
     * 通过key名字执行安卓按键事件
     *
     * @param keyName 输入键名，比如BACK、ENTER
     */
    public boolean appiumKeyeventByName(String keyName) {
        try {
            KeyEvent key = new KeyEvent();
            AndroidKey akey = AndroidKey.valueOf(keyName);
            driver.pressKey(key.withKey(akey));
            return true;
        } catch (IllegalArgumentException e) {
            log.error("按键失败", e.fillInStackTrace());
            return false;
        }
    }

    /**************************************截图操作***************************************/
    /***
     * 截取整个手机屏幕截图
     * @param filename
     */
    public void takeScreenshot(String filename) {
        try {
            filename += dateStr("MMdd-HHmmss") + ".png";
            filename = "Logs\\pic\\" + filename;
            log.info("正在截取图片" + filename);
            //截图保存为jvm内存中的文件
            File screenshotAs = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
            //在硬盘中创建一个文件，将截图复制过去。
            File picFile = new File(filename);
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截图失败", e.fillInStackTrace());
        }
    }


    /**
     * 将元素截为图片
     *
     * @param xpath
     * @param filename
     */
    public void takeEleShot(String xpath, String filename) {
        try {
            //截取指定元素的图片
            filename += dateStr("MMdd-HHmmss") + ".png";
            filename = "Logs\\pic\\" + filename;
            File screenshotAs = driver.findElement(By.xpath(xpath)).getScreenshotAs(OutputType.FILE);
            File picFile = new File(filename);
            Files.copy(screenshotAs, picFile);
        } catch (IOException e) {
            log.error("截图失败", e.fillInStackTrace());
        }
    }


    /*****************************退出操作**************************************/
    public boolean closeApp() {
        try {
            driver.quit();
            return true;
        } catch (Exception e) {
            log.error("关闭APP失败", e.fillInStackTrace());
            return false;
        }
    }

    /**
     * 关闭server。
     *
     * @return
     */
    public boolean killAppiumServer() {
        try {
            Runtime.getRuntime().exec("taskkill /f /im node.exe");
            log.info("appiumserver进程被杀死");
            return true;
        } catch (IOException e) {
            log.error("关闭appium服务端失败", e.fillInStackTrace());
            return false;
        }
    }

}